package cvar.model.transaction;

import java.util.HashMap;
import java.util.Map;

import cvar.model.Constraint;
import cvar.model.LPModel;
import cvar.model.LPModelBuilder;
import cvar.model.Sampler;
import cvar.model.Variable;

public class TransactionModelBuilder {

  private LPModelBuilder modelBuilder_;
  private Map<Product, Variable> map_;
  private Variable[] hourDiffVariables_;
  private Variable[] zkVariables;
  private double[] hpfc_;
  private double[] needs_;
  private static final double ALPHA = 0.95d;
  private static final int samplesNum = 3000;
  private Variable var;
  
  public TransactionModelBuilder(double[] hpfc, double[] needs) {
    modelBuilder_ = new LPModelBuilder();
    map_ = new HashMap<Product, Variable>();
    hourDiffVariables_ = new Variable[hpfc.length * 2];
    hpfc_ = hpfc;
    needs_ = needs;
    zkVariables = new Variable[samplesNum];
    var = modelBuilder_.addNonNegativeVariable("VAR");
  }
  
  public LPModel build() {
    createProducts();
    createModelEqualities();
    createCVarInequalities();
    setWeights();
    return modelBuilder_.build();
  }

  private void setWeights() {
    modelBuilder_.setVariableWeight(var, 1);
    for (int i = 0; i < samplesNum; i++) {
      modelBuilder_.setVariableWeight(zkVariables[i], (1 / ((1 - ALPHA) * samplesNum)));
    }
  }

  private void createCVarInequalities() {
    Sampler sampler = new Sampler(hpfc_);
    for (int i = 0; i < samplesNum; ++i) {
      // 0 >= f(x,E) - gamma - z_k
      Variable zk = modelBuilder_.addNonNegativeVariable("z_" + i);
      zkVariables[i] = zk;
      Constraint ieq = modelBuilder_.addInequality();
      ieq.setB(0);
      ieq.setVariableCoefficient(var, -1);
      ieq.setVariableCoefficient(zk, -1);
      addFValue(ieq, sampler.generate());
    }
  }

  private void addFValue(Constraint ieq, double[] hpfcSample) {
    for (int hour = 0; hour < hpfcSample.length; hour++) {
      double price = hpfcSample[hour];
      ieq.setVariableCoefficient(hourDiffVariables_[2 * hour], price);
      ieq.setVariableCoefficient(hourDiffVariables_[2 * hour + 1], price);
    }
  }

  private void createModelEqualities() {
    Constraint[] equalities = new Constraint[hpfc_.length];
    for (int i = 0; i < equalities.length; i++) {
      equalities[i] = modelBuilder_.addEquality();
      equalities[i].setB(needs_[i]);
      Variable hourDiffPlus = modelBuilder_.addNonNegativeVariable("hourDiffPlus_" + i);
      Variable hourDiffMinus = modelBuilder_.addNonNegativeVariable("hourDiffMinus_" + i);
      hourDiffVariables_[2 * i] = hourDiffPlus;
      hourDiffVariables_[2 * i + 1] = hourDiffMinus;
      equalities[i].setVariableCoefficient(hourDiffPlus, 1.0);
      equalities[i].setVariableCoefficient(hourDiffMinus, -1.0);
    }
    for (Product product : map_.keySet()) {
      for (int hour : product.getHours()) {
        if (hour < hpfc_.length) {
          equalities[hour].setVariableCoefficient(map_.get(product), 1.0);
        }
      }
    }
  }
  
  private void createProducts() {
    for (int i = 0; i < hpfc_.length ; i++) {
      if (0 == i % 24) {
        for (int j = 3; j < 6; ++j) {
          addProduct(Product.makeProduct(Product.Type.DAILY, i, j));
        }
      }
      if (0 == i % (24 * 7)) {
        for (int j = 3; j < 6; ++j) {
          addProduct(Product.makeProduct(Product.Type.WEEKLY, i, j));
        }
      }
      if (0 == i % (24 * 30)) {
        for (int j = 3; j < 6; ++j) {
          addProduct(Product.makeProduct(Product.Type.MONTHLY, i, j));
        }
      }
      if (0 == i % (24 * 30 * 4)) {
        for (int j = 1; j < 4; ++j) {
          addProduct(Product.makeProduct(Product.Type.QUARTERLY, i, j));
        }
      }
    }
  }

  void addProduct(Product product) {
    map_.put(product, modelBuilder_.addNonNegativeVariable(product.getName()));
  }

}
